package com.yunkeji.api.sign.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;

import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yunkeji.api.common.service.SysParamService;
import com.yunkeji.api.sign.service.SignService;
import com.yunkeji.api.sign.vo.SignConfigVo;
import com.yunkeji.api.user.mapper.ApiAppUserMapper;
import com.yunkeji.common.constant.CacheConstants;
import com.yunkeji.common.core.domain.dto.PayLogDTO;
import com.yunkeji.common.enums.app.PayLogTypeEnums;
import com.yunkeji.common.exception.AppServiceException;
import com.yunkeji.common.helper.LoginHelper;
import com.yunkeji.dao.domain.AppUser;
import com.yunkeji.dao.domain.AppUserVip;
import com.yunkeji.dao.domain.UserSign;
import com.yunkeji.dao.mapper.AppUserExtMapper;
import com.yunkeji.dao.mapper.AppUserSignMapper;
import com.yunkeji.dao.mapper.PayLogMapper;

import lombok.AllArgsConstructor;

import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author 老张
 */
@Service
@AllArgsConstructor
public class SignServiceImpl implements SignService {

    private final SysParamService sysParamService;
    private final ApiAppUserMapper apiAppUserMapper;
    private final AppUserSignMapper appUserSignMapper;
    private final AppUserExtMapper appUserExtMapper;
    private final PayLogMapper payLogMapper;

    @Lock4j(
            name = CacheConstants.LOCK_SIGN,
            keys = {"#uid"})
    @Override
    public void toSign(Long uid) {
        AppUser appUser = apiAppUserMapper.selectById(uid);

        AppUserVip appUserVip = AppUserVip.builder().build().selectById(appUser.getId());
        Date vipExpireTime = appUserVip.getCommonVipExpireTime();
        if (new Date().getTime() >= vipExpireTime.getTime()) {
            throw AppServiceException.paramException("只有会员才可以参与签到");
        }
        // yyyyMMdd
        Integer today = Integer.valueOf(DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN));
        UserSign userSign =
                appUserSignMapper.selectOne(
                        Wrappers.lambdaQuery(UserSign.class)
                                .eq(UserSign::getSignDate, today)
                                .eq(UserSign::getUserId, LoginHelper.getUserId()));
        if (userSign != null) {
            throw AppServiceException.paramException("今日已经参与了签到");
        }
        // 计算连续签到天数
        SignConfigVo signDays = config();

        BigDecimal step = new BigDecimal(sysParamService.selectConfigByKey("sign_reward_step"));

        Integer signDay = signDays.getSignDay();
        // 即将获得的奖励
        BigDecimal multiply = step.multiply(BigDecimal.valueOf(signDay + 1));

        // 创建签到记录
        UserSign sign = new UserSign();
        sign.setSignDate(today);
        sign.setGoldNum(multiply);
        sign.setUserId(LoginHelper.getUserId());
        appUserSignMapper.insert(sign);

        apiAppUserMapper.logForAddGoldBalance(
                PayLogDTO.builder()
                        .traceId(sign.getId())
                        .logType(PayLogTypeEnums.收益_会员签到)
                        .serviceTag("用户签到:" + sign.getId())
                        .changeNum(multiply)
                        .uid(LoginHelper.getUserId())
                        .build());
    }

    @Override
    public SignConfigVo config() {
        Date date = new Date();
        // 按周分组,每七天一组,月底不足七天向前补齐
        SignConfigVo config = groupDatesByWeek(date);
        // yyyyMMdd-今天日期
        Integer today = Integer.parseInt(DateUtil.format(date, DatePattern.PURE_DATE_PATTERN));
        // yyyyMM -本月开始日期
        String currentMonthStart =
                DateUtil.format(DateUtil.beginOfMonth(date), DatePattern.PURE_DATE_PATTERN);
        // yyyyMMdd-昨天日期
        Integer lastDay =
                Integer.parseInt(
                        DateUtil.format(DateUtil.yesterday(), DatePattern.PURE_DATE_PATTERN));
        // 截止今天的连续签到天数
        List<UserSign> userSigns =
                appUserSignMapper.selectList(
                        Wrappers.lambdaQuery(UserSign.class)
                                // 大于等于当前月的一号
                                .ge(UserSign::getSignDate, currentMonthStart)
                                // 小于等于昨天
                                .le(UserSign::getSignDate, today)
                                .eq(UserSign::getUserId, LoginHelper.getUserId())
                                // 根据签到日期排序-降序
                                .orderByDesc(UserSign::getSignDate)
                                // 最近31次签到数据
                                .last(" limit 31"));

        getContinueSignDays(userSigns, config);
        fillSignDays(config, userSigns);

        return config;
    }

    private void fillSignDays(SignConfigVo config, List<UserSign> userSigns) {
        // yyyyMMdd-今天日期
        Integer today =
                Integer.parseInt(DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_PATTERN));
        // yyyyMMdd-昨天日期
        Integer yesterday =
                Integer.parseInt(
                        DateUtil.format(DateUtil.yesterday(), DatePattern.PURE_DATE_PATTERN));

        List<SignConfigVo.Sign> signs = config.getSigns();
        Map<Integer, UserSign> collect =
                userSigns.stream().collect(Collectors.toMap(UserSign::getSignDate, item -> item));
        signs.forEach(
                item -> {
                    UserSign userSign = collect.get(item.getSignDate());
                    // 为空说明未签到
                    if (userSign == null) {
                        item.setSignFlag(false);
                        Integer signDate = item.getSignDate();

                        if (signDate.equals(today)) {}

                    } else {
                        // 已经签到填充获得的奖励
                        item.setReward(userSign.getGoldNum());
                        item.setSignFlag(true);
                    }
                });
    }

    /**
     * 获取连续签到的天数
     *
     * @author 老张
     */
    private void getContinueSignDays(List<UserSign> userSigns, SignConfigVo config) {

        // yyyyMMdd-今天日期
        Integer today =
                Integer.parseInt(DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_PATTERN));
        // yyyyMMdd-昨天日期
        Integer yesterday =
                Integer.parseInt(
                        DateUtil.format(DateUtil.yesterday(), DatePattern.PURE_DATE_PATTERN));
        config.setToday(today);
        if (CollectionUtil.isEmpty(userSigns)) {
            // 预测从今天起每天可获得的金币
            predict(config, today, 0);
            config.setSignDay(0);
            return;
        }
        // 今天是否签到
        boolean todaySign = userSigns.stream().anyMatch(item -> item.getSignDate().equals(today));
        // 昨天是否签到
        boolean lastSign =
                userSigns.stream().anyMatch(item -> item.getSignDate().equals(yesterday));
        // 昨天没签
        if (!lastSign) {
            if (todaySign) {
                // 今天签了,从明天开始预测,index+1
                predict(config, today + 1, 1);
                config.setSignDay(1);
                return;
            } else {
                // 今天没签
                predict(config, today, 0);
                config.setSignDay(0);
                return;
            }
        }
        Integer signDay = 0;
        Integer currentDay = today;
        if (!todaySign) {
            currentDay = yesterday;
        }
        for (int i = 0; i < userSigns.size(); i++) {
            UserSign userSign = userSigns.get(i);
            Integer signDate = userSign.getSignDate();
            if (signDate.equals(currentDay)) {
                signDay++;
                currentDay--;
            } else {
                break;
            }
        }
        if (todaySign) {
            // 今天签了
            predict(config, today + 1, signDay + 1);
        } else {
            predict(config, today, signDay);
        }
        config.setSignDay(signDay);
    }

    /**
     * 未签到预计可获得的金币
     *
     * @param today 从哪一天开始预测
     * @param index 初始步长索引值
     * @author 老张
     */
    private void predict(SignConfigVo config, Integer today, Integer index) {
        List<SignConfigVo.Sign> signs = config.getSigns();
        BigDecimal step = new BigDecimal(sysParamService.selectConfigByKey("sign_reward_step"));
        for (int i = 0; i < signs.size(); i++) {
            SignConfigVo.Sign sign = signs.get(i);
            Integer signDate = sign.getSignDate();
            if (signDate >= today) {
                Integer realIndex = signDate - today + index;
                // 预计奖励=步长+(步长*差值)
                sign.setReward(step.add(step.multiply(BigDecimal.valueOf(realIndex))));
            }
        }
    }

    /**
     * 按周分组，返回这一组的日期数据，必定返回七条，如果月末不足，会返回前面的日期补齐
     *
     * @param inputDate
     * @return
     */
    public SignConfigVo groupDatesByWeek(Date inputDate) {
        // 这是本月的第几天
        int day = DateUtil.dayOfMonth(inputDate);
        BigDecimal currentGroup =
                BigDecimal.valueOf(day).divide(BigDecimal.valueOf(7), 0, RoundingMode.UP);
        // 本月的开始日期
        DateTime startDateCurrentMonth = DateUtil.beginOfMonth(inputDate);
        // 计算分组的开始索引
        Integer startIndex = currentGroup.subtract(BigDecimal.ONE).intValue() * 7;
        // 计算分组的结束索引
        Integer endIndex = currentGroup.intValue() * 7;
        // 本组的开始日期
        DateTime startDate = DateUtil.offsetDay(startDateCurrentMonth, startIndex);
        DateTime endDate = DateUtil.offsetDay(startDateCurrentMonth, endIndex - 1);
        if (DateUtil.month(endDate) != DateUtil.month(inputDate)) {
            // 到了下个月了
            // 计算相差几天
            long between =
                    DateUtil.between(endDate, DateUtil.endOfMonth(inputDate), DateUnit.DAY) + 1;
            startDate = DateUtil.offsetDay(startDate, Integer.valueOf(String.valueOf(-between)));
        }
        List<SignConfigVo.Sign> dateList = new LinkedList<>();

        for (int i = 0; i < 7; i++) {
            DateTime dateTime = DateUtil.offsetDay(startDate, i);
            int currentDay = DateUtil.dayOfMonth(dateTime);
            SignConfigVo.Sign sign = new SignConfigVo.Sign();
            sign.setDay(currentDay);
            sign.setSignDate(
                    Integer.parseInt(DateUtil.format(dateTime, DatePattern.PURE_DATE_PATTERN)));
            sign.setSignFlag(false);
            dateList.add(sign);
        }

        return SignConfigVo.builder().signs(dateList).build();
    }
}
